60 协方差分析
本章定位(复习与补充训练层):本章对应正课第7章《NumPy 矩阵处理》(章节 7,协方差矩阵与相关系数矩阵的计算)以及第21章(章节 21)、第34章(章节 34)的相关性分析主线,用于复习、补缺与额外练习。建议先不看讲解,直接尝试下方平台任务,再对照解析补弱项。本章不属于必修主线。先做本章『动手与思考』第 1 题与平台任务自测,通过即可跳过本章。
60.1 引言变量关系的度量
协方差(Covariance)和相关系数(Correlation)是衡量两个变量关系的基础统计量,金融中用于量化组合风险、选择低相关资产做分散化、识别驱动收益的共同因子并寻找对冲工具。本章正文集中讲授二者的度量及其在组合风险中的应用;平台任务2公式 sales ~ points + C(market) * C(warranty) 涉及的协方差分析(ANCOVA,把 market、warranty 作为因子纳入回归并考察交互项)属进阶内容,正文不展开,可在掌握线性回归与方差分析后进一步学习。
60.2 本章学习目标
通过本章复习,你将能够:
- 写出样本协方差与Pearson相关系数的公式,说明二者在量纲、取值范围与可比性上的差异,并复述 \(\text{Cov}(X,X)=\text{Var}(X)\)、对称性、双线性三条性质
- 用
DataFrame.cov()与.corr()计算协方差矩阵与相关系数矩阵,解读矩阵的对称性与对角线元素的含义 - 用矩阵形式 \(\sigma_p^2=\mathbf{w}^T\Sigma\mathbf{w}\) 计算组合方差,并从 \(\rho_{ij}\le1\) 出发解释风险分散化的数学来源
- 识别并修复平台任务1、任务2中的典型笔误(缺失导入、减号代等号、变量名含空格、赋值号右侧悬空),并在本地用同结构数据复现相关系数的计算与判断
- 区分简单相关与偏相关,并说明滚动相关系数如何刻画相关性的时变特征
60.3 数学基础
60.3.1 协方差
定义:
\[ \text{Cov}(X, Y) = E[(X - \mu_X)(Y - \mu_Y)] \]
样本协方差:
\[ \text{Cov}(X, Y) = \frac{1}{n-1}\sum_{i=1}^n (x_i - \bar{x})(y_i - \bar{y}) \]
性质:
- \(\text{Cov}(X, X) = \text{Var}(X)\)
- \(\text{Cov}(X, Y) = \text{Cov}(Y, X)\) (对称性)
- \(\text{Cov}(aX, bY) = ab \cdot \text{Cov}(X, Y)\) (双线性)
60.3.2 相关系数
Pearson相关系数:
\[ \rho_{XY} = \frac{\text{Cov}(X, Y)}{\sigma_X \sigma_Y} = \frac{\sum(x_i - \bar{x})(y_i - \bar{y})}{\sqrt{\sum(x_i - \bar{x})^2}\sqrt{\sum(y_i - \bar{y})^2}} \]
性质:
- \(-1 \leq \rho \leq 1\)
- \(\rho = 1\):完全正相关
- \(\rho = -1\):完全负相关
- \(\rho = 0\):无线性相关(可能有非线性关系)
60.4 协方差矩阵计算
任务要求(归纳自块内任务注释与代码;题面含已知笔误):从平台内置的 sale_points.csv 读取数据,并用 head() 查看前 5 行。注意题面存在两处笔误与一处缺失——读取行以减号代替等号赋值、变量名 sale points 中间含空格、缺少 import pandas;请按平台原始题面原样输入(注释除外),判定以平台为准,本地演练见下方修正版。
平台任务1(平台原始代码)
以下为平台原始代码,含需在平台完成的留空与已知笔误:
# 注:sale_points.csv数据文件本地没有,但平台已经内置;同结构真实数据可由任务二代码中的 OSS 直链获得(24×4,market/warranty/sales/points)
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
# 注:该代码块包含未完成的填空/已知笔误(如变量名、赋值号),请按平台原始题面原样输入,以平台判定为准
sale_points-pd.read_csv('sale_points.csv') # 从CSV文件读取数据
sale points.head() # 查看sale points前5行数据预期输出(数据文件为教学平台内置,本机无法复现,判读要点如下;以平台运行结果为准):按题面原样输入无法在本地运行——sale_points-pd.read_csv(...) 会被解析为减法运算而非赋值,sale points 也不是合法变量名。正确补全后,输出为 sale_points 的前 5 行,含 market(所在市场)、warranty(保修期)、sales(销售额)、points(促销积分)四列;同结构的真实数据(24 行 × 4 列)可由下方平台任务2任务二代码中的 OSS 直链获得,块首注记已作说明。
下方的本地演练版给出修正后可在本地运行的形式,共三处修正:补上 import pandas、把减号改为等号赋值、去掉变量名中的空格。由于 sale_points.csv 仅平台内置,本地演练以一份内联的小样本数据代替该文件——列结构与平台任务2的任务二所用数据一致(促销积分 points、销售额 sales、所在市场 market、保修期 warranty),取值为演示语法而设的模拟值:
# 本地演练说明:平台内置的sale_points.csv本地没有,以下用同结构的内联模拟小样本代替
import pandas as pd # 修正1:平台题面缺少导入语句,本地需先导入Pandas
sale_points = pd.DataFrame({ # 修正2:用等号赋值,原题的减号是笔误;数据以内联DataFrame代替read_csv
'points': [30, 33, 37, 40, 44, 47, 50, 53, 57, 60], # 促销积分(连续变量)
'sales': [88, 94, 101, 106, 116, 120, 128, 133, 141, 148], # 月销售额,万元(连续变量)
'market': ['market 1']*4 + ['market 2']*3 + ['market 3']*3, # 所在市场(分类变量)
'warranty': ['1 years', '3 years']*5 # 保修期方案(分类变量)
})
print(sale_points.head()) # 修正3:变量名中不能有空格,原题的sale points是笔误任务要求(归纳自块内任务注释与代码;题面含悬空赋值笔误):
- 任务一:从平台内置的”相关分析sales.csv”读取数据,补全
corr =赋值号右侧(计算前两列的皮尔逊相关系数);用data.corr()计算相关性矩阵,以imshow绘制热力图并在格内标注保留 2 位小数的相关系数,保存为”热力图.png”;输出该相关系数,并按abs(corr) >= 0.7输出是否存在显著的线性相关性 - 任务二:从 OSS 直链读取 sale_points 数据,把
market、warranty转为分类变量并重命名类别标签;补全myfont=赋值号右侧后,用smf.ols拟合sales ~ points + C(market) * C(warranty),输出回归摘要
请按平台原始题面原样输入(注释除外),判定以平台为准;两处悬空赋值为题面留空,补全内容以平台判定为准。
平台任务2(平台原始代码)
以下为平台原始代码,含需在平台完成的留空与已知笔误:
# 注:相关分析sales.csv数据文件本地没有,但平台已经内置;任务二所用的sale_points同结构真实数据可由本块代码中的 OSS 直链获得(24×4,market/warranty/sales/points)
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
# 注:该代码块包含未完成的填空/已知笔误(如变量名、赋值号),请按平台原始题面原样输入,以平台判定为准
#任务一
import pandas as pd
import matplotlib.pyplot as plt # 导入Matplotlib绘图库
import numpy as np # 导入NumPy数值计算库
# 读取数据
data = pd.read_csv('相关分析sales.csv')
# 计算皮尔逊相关系数
corr =
# 计算相关性矩阵
corr_matrix = data.corr()
# 绘制热力图
fig, ax = plt.subplots()
im = ax.imshow(corr_matrix, cmap='YlGnBu') # 显示热力图矩阵
# 显示相关系数的值
for i in range(corr_matrix.shape[0]):
for j in range(corr_matrix.shape[1]): # 遍历range(corr_matrix.shape[1])中的每个j
text = ax.text(j, i, round(corr_matrix.iloc[i, j],2), # 按位置索引提取显示相关系数的值
ha="center", va="center", color="black") # 设置标注文本的水平和垂直对齐方式
# 设置坐标轴标签和标题
ax.set_xticks(np.arange(len(corr_matrix.columns)))
ax.set_yticks(np.arange(len(corr_matrix.columns))) # 设置Y轴刻度标签
ax.set_xticklabels(corr_matrix.columns) # 设置X轴刻度标签
ax.set_yticklabels(corr_matrix.columns) # 设置Y轴刻度标签
ax.set_title("Correlation Heatmap") # 设置图表标题
# 在图形旁添加颜色条
cbar = ax.figure.colorbar(im, ax=ax)
# 显示图形
plt.savefig("热力图.png")
# 输出结果
print('Pearson correlation coefficient: ', corr)
if abs(corr) >= 0.7: # 条件判断:abs(corr) >= 0.7
print('存在显著的线性相关性') # 输出存在显著的线性相关性
else: # 不满足以上条件时
print('不存在显著的线性相关性') # 输出不存在显著的线性相关性
#任务二
import numpy as np
from scipy import stats # 导入SciPy科学计算库
import pandas as pd # 导入Pandas数据分析库
import statsmodels.api as sm # 导入统计建模库
import statsmodels.formula.api as smf # 导入统计建模库
import matplotlib.pyplot as plt # 导入Matplotlib绘图库
from statsmodels.stats.multicomp import pairwise_tukeyhsd # 导入统计建模库
from statsmodels.graphics.api import interaction_plot # 导入统计建模库
from matplotlib.font_manager import FontProperties # 导入Matplotlib库
myfont= # 设置中文字体属性
# 从CSV文件读取数据存入sale_points
sale_points = pd.read_csv(u'https://huoran.oss-cn-shenzhen.aliyuncs.com/20221116/csv/1592817699758039040.csv',encoding = "gbk")
sale_points['market'] = sale_points['market'].astype('category') # 转换数据类型
# 定义列表sale_points['market'].cat.categories
sale_points['market'].cat.categories=['market 1', 'market 2', 'market 3']
sale_points['warranty'] = sale_points['warranty'].astype('category') # 转换数据类型
# 定义列表sale_points['warranty'].cat.categories
sale_points['warranty'].cat.categories=['1 years', '3 years']
print(sale_points.head()) # 输出前几行数据
formula = 'sales ~ points + C(market) * C(warranty)' # 定义模型公式:sales ~ points + C(market) * C(warranty)
sale_points_anova_cov_est = smf.ols(formula, data = sale_points).fit() # dc_sales_est 是一个模型对象
print(sale_points_anova_cov_est.summary()) # 输出合计值预期输出(判读要点;平台内置数据文件与题面留空处本机无法原样复现,以平台运行结果为准):任务一生成热力图文件”热力图.png”(对角线为 1.0,其余格标注两两相关系数)、一行 Pearson correlation coefficient: 及其数值,并按 0.7 阈值输出”存在/不存在显著的线性相关性”,具体数值取决于平台内置文件的前两列。任务二的 OSS 直链数据本机已拉取验证(24 行 × 4 列,gbk 编码),类别重命名后 head() 前两行为 market 1/1 years 的 26.0、1.8 与 22.0、1.1;本机以 pandas 2.x 的 rename_categories 等价替换 .cat.categories= 赋值并略去未使用的 myfont 行后实跑(标签重命名不影响回归结果),OLS 摘要关键实测值:R-squared=0.943、Adj. R-squared=0.923、F=47.05(Prob=1.16e-09),points 系数 8.5873(p<0.001),市场 2 相对市场 1 为 -8.0349(p<0.001),保修 3 年主效应 3.0485(p=0.086,不显著),交互项 market 2×3 年为 5.4217(p=0.043)、market 3×3 年为 14.0594(p<0.001)。
平台任务2中有两处悬空赋值(corr =与myfont=),下方的本地演练版给出补全后可在本地运行的形式。由于 相关分析sales.csv 仅平台内置,本地以同结构的内联数据代替——前两列为待分析的两个数值变量,与平台代码 data.iloc[:, 0].corr(data.iloc[:, 1]) 的取列口径一致:
# 本地演练说明:平台内置的相关分析sales.csv本地没有,以下用同结构的内联模拟小样本代替
import pandas as pd # 导入Pandas数据分析库
from matplotlib.font_manager import FontProperties # 导入字体属性设置类
data = pd.DataFrame({ # 本地以内联DataFrame代替pd.read_csv读取平台数据文件
'points': [30, 33, 37, 40, 44, 47, 50, 53, 57, 60], # 促销积分(第一个数值变量)
'sales': [88, 94, 101, 106, 116, 120, 128, 133, 141, 148] # 月销售额,万元(第二个数值变量)
})
corr = data.iloc[:, 0].corr(data.iloc[:, 1]) # 修正:补全corr赋值号右侧,计算前两列的皮尔逊相关系数
myfont = FontProperties(family=['Source Han Serif SC', 'SimHei']) # 修正:补全myfont赋值号右侧,创建中文字体属性对象(规范字体在前)
print('皮尔逊相关系数:', round(corr, 4)) # 输出相关系数,检验补全结果
print('abs(corr) >= 0.7:', abs(corr) >= 0.7) # 按平台任务的口径判断是否存在显著的线性相关性# =============================================================================
# 题目: 协方差矩阵与相关系数矩阵
# =============================================================================
# 本代码展示如何计算和解释协方差矩阵及相关系数矩阵
# 应用于金融场景:分析多资产收益率之间的关系,为投资组合构建提供基础
# ==================== 导入必要的库 ====================
import pandas as pd # 数据分析库
import numpy as np # 数值计算库
import matplotlib.pyplot as plt # 绘图库
import seaborn as sns # 统计绘图库
# ==================== 设置中文字体 ====================
plt.rcParams['font.sans-serif'] = ['Source Han Serif SC', 'SimHei'] # 规范字体在前,SimHei 仅作后备 # 设置中文字体为黑体
plt.rcParams['axes.unicode_minus'] = False # 解决负号显示问题
# ==================== 创建多资产收益率数据 ====================
np.random.seed(42) # 设置随机种子,确保结果可复现
n_days = 500 # 模拟500个交易日(约2年)
# 定义各资产的期望收益率
mean_returns = [0.001, 0.0008, 0.0012, 0.0005]
# 贵州茅台:0.1%, 五粮液:0.08%, 招商银行:0.12%, 中国平安:0.05%
# ==================== 设定真实的协方差矩阵 ====================
cov_matrix_true = np.array([
[0.0004, 0.0002, 0.00015, 0.0001], # 贵州茅台的协方差
[0.0002, 0.0003, 0.0001, 0.00005], # 五粮液的协方差
[0.00015, 0.0001, 0.00035, 0.00008],# 招商银行的协方差
[0.0001, 0.00005, 0.00008, 0.0002] # 中国平安的协方差
])
# 对角线元素是各资产的方差,非对角线元素是协方差
# 茅台和五粮液协方差最大(0.0002),因为都是白酒股,高度相关
# ==================== 生成多元正态分布数据 ====================
returns = np.random.multivariate_normal(mean_returns, cov_matrix_true, n_days)
# 根据均值和协方差矩阵生成多元正态分布的收益率数据
# n_days: 生成500个观测值
# 返回形状为(500, 4)的数组,每行代表一天的4个资产收益率
# ==================== 转换为DataFrame ====================
assets = ['贵州茅台', '五粮液', '招商银行', '中国平安'] # 资产名称
df_returns = pd.DataFrame(returns, columns=assets)
# 将NumPy数组转换为DataFrame,列名为资产名称
print('收益率数据(前10行):') # 打印标题
print(df_returns.head(10)) # 显示前10行数据
# ==================== 计算协方差矩阵 ====================
cov_matrix = df_returns.cov() # 计算协方差矩阵
# .cov()方法计算每列之间的协方差
# 返回4×4的对称矩阵
print('\n协方差矩阵:') # 打印标题
print(cov_matrix.round(6)) # 打印协方差矩阵,保留6位小数
# ==================== 计算相关系数矩阵 ====================
corr_matrix = df_returns.corr() # 计算Pearson相关系数矩阵
# .corr()方法标准化协方差,得到-1到1的相关系数
print('\n相关系数矩阵:') # 打印标题
print(corr_matrix.round(4)) # 打印相关系数矩阵,保留4位小数协方差 vs 相关系数:
| 特性 | 协方差 | 相关系数 |
|---|---|---|
| 量纲 | 有单位(如%²) | 无量纲 |
| 范围 | \((-\infty, +\infty)\) | \([-1, 1]\) |
| 标准化 | 否 | 是 |
| 可比性 | 不同资产对不可比 | 所有资产对可比 |
| 应用 | 计算组合方差 | 衡量关系强度 |
60.5 相关性可视化热力图
# =============================================================================
# 题目: 相关系数矩阵热力图
# =============================================================================
# 本代码展示如何用热力图可视化相关系数矩阵
# 应用于金融场景:快速识别资产间相关性的强弱模式
# ==================== 绘制热力图 ====================
plt.figure(figsize=(10, 8)) # 创建10×8英寸画布
sns.heatmap(corr_matrix, # 绘制热力图,输入相关系数矩阵
annot=True, # annot=True: 在每个格子中显示数值
fmt='.2f', # fmt='.2f': 数值格式,保留2位小数
cmap='RdYlGn', # cmap='RdYlGn': 颜色方案,红-黄-绿发散色图
center=0, # center=0: 设置颜色中心为0,使正负相关对称
square=True, # square=True: 每个格子为正方形
linewidths=0.5, # linewidths=0.5: 格子之间的间隔线宽0.5
cbar_kws={'label': '相关系数'}, # cbar_kws: 颜色条参数,设置标签
vmin=-1, vmax=1) # vmin=-1, vmax=1: 设置颜色范围为-1到1
# ==================== 添加标题 ====================
plt.title('资产收益率相关系数矩阵', fontsize=16, fontweight='bold', pad=20)
plt.tight_layout()
plt.show()
# ==================== 找出相关性最高的组合 ====================
# 提取上三角矩阵(排除对角线),避免重复计算
mask = np.triu(np.ones_like(corr_matrix, dtype=bool), k=1)
# np.ones_like: 创建与corr_matrix形状相同的全1矩阵
# np.triu(..., k=1): 提取上三角矩阵,排除对角线(k=1表示从第一条对角线以上开始)
corr_upper = corr_matrix.where(mask) # 只保留上三角部分,其他置为NaN
# 找最大和最小相关系数
max_corr = corr_upper.max().max() # 最大相关系数
min_corr = corr_upper.min().min() # 最小相关系数
max_pair = corr_upper.stack().idxmax() # 最大相关系数对应的资产对
min_pair = corr_upper.stack().idxmin() # 最小相关系数对应的资产对
# .stack(): 将DataFrame转换为Series(多级索引)
# .idxmax()/.idxmin(): 找最大/最小值的索引
print(f'最高相关性: {max_pair} = {max_corr:.4f}') # 打印最相关的资产对
print(f'最低相关性: {min_pair} = {min_corr:.4f}') # 打印最不相关的资产对热力图配色方案:
| 方案 | 特点 | 适用场景 |
|---|---|---|
RdYlGn |
红-黄-绿,中心为0 | 一般相关性 |
coolwarm |
蓝-白-红,中心为0 | 科学出版 |
viridis |
紫-黄,感知均匀 | 色盲友好 |
RdBu |
红-蓝,发散 | 正负对比明显 |
60.6 组合风险与协方差
# =============================================================================
# 题目: 不同权重下的组合风险
# =============================================================================
# 本代码展示如何计算投资组合的风险,并可视化有效前沿
# 应用于金融场景:投资组合优化,寻找最优风险收益平衡
# ==================== 定义资产 ====================
assets_list = ['贵州茅台', '五粮液', '招商银行', '中国平安'] # 资产列表
n_assets = len(assets_list) # 资产数量
# ==================== 计算年化协方差矩阵 ====================
cov_annual = cov_matrix * 252 # 日数据转年化
# 252是一年的交易日数量
# 日协方差乘以252得到年化协方差
# ==================== 生成不同的权重组合 ====================
np.random.seed(42) # 设置随机种子
n_portfolios = 1000 # 生成1000个随机组合
weights_list = [] # 存储权重
returns_list = [] # 存储收益率
risks_list = [] # 存储风险
for _ in range(n_portfolios): # 遍历每个组合
# 随机生成权重(和为1)
weights = np.random.random(n_assets) # 生成4个0-1之间的随机数
weights = weights / np.sum(weights) # 归一化,使权重和为1
# ==================== 组合期望收益 ====================
portfolio_return = np.sum(df_returns.mean() * weights) * 252
# 组合收益率 = 各资产收益率的加权平均
# .mean()计算日收益率,乘以252得到年化收益率
# ==================== 组合方差(使用矩阵形式) ====================
portfolio_variance = np.dot(weights.T, np.dot(cov_annual, weights))
# 组合方差 = w^T * Σ * w
# np.dot(weights.T, ...): 权重向量的转置
# np.dot(cov_annual, weights): 协方差矩阵乘以权重向量
portfolio_std = np.sqrt(portfolio_variance) # 标准差 = 方差的平方根
weights_list.append(weights) # 存储权重
returns_list.append(portfolio_return) # 存储收益率
risks_list.append(portfolio_std) # 存储风险
# ==================== 转换为DataFrame ====================
df_portfolios = pd.DataFrame({
'收益率': returns_list,
'风险(标准差)': risks_list
})
# ==================== 可视化有效前沿 ====================
plt.figure(figsize=(12, 8)) # 创建画布
scatter = plt.scatter(df_portfolios['风险(标准差)'], df_portfolios['收益率'],
c=df_portfolios['收益率'] / df_portfolios['风险(标准差)'],
# 颜色映射夏普比率 = 收益率/风险
cmap='viridis', alpha=0.6, s=50) # viridis色阶,半透明,点大小50
plt.colorbar(scatter, label='夏普比率') # 添加颜色条
# ==================== 标记单个资产 ====================
for i, asset in enumerate(assets_list): # 遍历每个资产
asset_return = df_returns[asset].mean() * 252 # 计算该资产年化收益率
asset_risk = np.sqrt(cov_annual.iloc[i, i]) # 计算该资产年化风险
# cov_annual.iloc[i, i]: 协方差矩阵的对角线元素(方差)
plt.scatter(asset_risk, asset_return, s=200, marker='*', # 标记为星号,大小200
color='red', edgecolors='black', linewidths=1.5) # 红色,黑色边缘
plt.text(asset_risk, asset_return, f' {asset}', fontsize=10) # 添加资产名称标签
# ==================== 添加标签和标题 ====================
plt.xlabel('年化风险(标准差)', fontsize=12)
plt.ylabel('年化收益率', fontsize=12)
plt.title('投资组合:风险与收益的关系', fontsize=16, fontweight='bold')
plt.grid(alpha=0.3)
plt.tight_layout()
plt.show()
# ==================== 找最优组合(最大夏普比率) ====================
max_sharpe_idx = (df_portfolios['收益率'] / df_portfolios['风险(标准差)']).idxmax()
# 找到夏普比率最大的组合索引
optimal_weights = weights_list[max_sharpe_idx] # 提取最优权重
print('\n最优组合权重(最大夏普比率):') # 打印标题
for asset, weight in zip(assets_list, optimal_weights): # 遍历资产和权重
print(f'{asset}: {weight:.2%}') # 打印每个资产的权重,百分比格式组合风险的数学推导:
对于 \(n\) 个资产的投资组合,权重为 \(w_1, w_2, \ldots, w_n\):
\[ \sigma_p^2 = \sum_{i=1}^n \sum_{j=1}^n w_i w_j \sigma_{ij} \]
矩阵形式:
\[ \sigma_p^2 = \mathbf{w}^T \Sigma \mathbf{w} \]
其中:
- \(\mathbf{w}\):权重向量 \((n \times 1)\)
- \(\Sigma\):协方差矩阵 \((n \times n)\)
分散化原理:
当 \(\rho_{ij} \leq 1\) 时:
\[ \sigma_p \leq \sum_{i=1}^n w_i \sigma_i \]
即组合标准差不超过各资产标准差的加权平均(\(\rho_{ij} = 1\) 时取等号)。只要资产间不完全同涨同跌(\(\rho_{ij} < 1\)),组合标准差就严格低于标准差的加权平均,实现风险分散;即使资产间是正的相关,组合标准差仍可被降低,只是降不到全部不相关(\(\rho_{ij} = 0\))时的水平 \(\sqrt{\sum_i w_i^2 \sigma_i^2}\) 那么低。
60.7 时间序列相关性滚动相关
# =============================================================================
# 题目: 滚动相关系数——相关性时变特征
# =============================================================================
# 本代码展示如何计算滚动相关系数,捕捉相关性的时变特征
# 应用于金融场景:识别市场状态转换,风险对冲时机选择
# ==================== 计算贵州茅台和五粮液的60日滚动相关系数 ====================
window = 60 # 滚动窗口大小:60个交易日(约3个月)
rolling_corr = df_returns['贵州茅台'].rolling(window).corr(df_returns['五粮液'])
# .rolling(60): 创建60个交易日的滚动窗口
# .corr(...): 计算窗口内两个序列的相关系数
# 返回一个Series,长度与原始数据相同
# ==================== 绘制滚动相关系数 ====================
plt.figure(figsize=(14, 6)) # 创建14×6英寸画布
plt.plot(df_returns.index, rolling_corr, linewidth=2, color='#2E86AB')
# 绘制滚动相关系数的时间序列
# ==================== 添加参考线 ====================
plt.axhline(y=0, color='black', linestyle='-', linewidth=0.5) # y=0水平线
plt.axhline(y=rolling_corr.mean(), color='red', linestyle='--',
linewidth=2, label=f'均值={rolling_corr.mean():.4f}')
# 均值参考线,红色虚线
# ==================== 填充正负相关区域 ====================
plt.fill_between(df_returns.index, rolling_corr, 0,
where=(rolling_corr >= 0), alpha=0.3, color='green', label='正相关期')
# 填充相关系数>0的区域,绿色半透明
plt.fill_between(df_returns.index, rolling_corr, 0,
where=(rolling_corr < 0), alpha=0.3, color='red', label='负相关期')
# 填充相关系数<0的区域,红色半透明
# ==================== 添加标题和标签 ====================
plt.title('滚动相关系数(60日窗口)', fontsize=16, fontweight='bold')
plt.xlabel('日期', fontsize=12)
plt.ylabel('相关系数', fontsize=12)
plt.legend(fontsize=11, loc='upper left') # 显示图例
plt.grid(alpha=0.3)
plt.tight_layout()
plt.show()
# ==================== 相关性稳定性分析 ====================
print(f'相关系数统计:')
print(f'均值: {rolling_corr.mean():.4f}') # 平均相关系数
print(f'标准差: {rolling_corr.std():.4f}') # 相关系数的波动性
print(f'最大值: {rolling_corr.max():.4f}') # 最高相关性
print(f'最小值: {rolling_corr.min():.4f}') # 最低相关性
# ==================== 识别相关性显著下降的时期 ====================
threshold = rolling_corr.mean() - 2 * rolling_corr.std()
# 阈值 = 均值 - 2倍标准差,低于此值视为异常
low_corr_periods = rolling_corr[rolling_corr < threshold] # 筛选低相关时期
print(f'\n相关性异常低的时期({len(low_corr_periods)}天):') # 打印低相关期的天数
print(low_corr_periods.head()) # 显示前几个低相关期滚动窗口选择:
- 短期(20日):捕捉快速变化,噪声大
- 中期(60日):平衡灵敏度和稳定性
- 长期(120日):平滑,识别长期关系
60.8 金融应用行业相关性网络
# =============================================================================
# 题目: 行业相关性网络可视化
# =============================================================================
# 本代码展示如何分析行业间的相关性结构
# 应用于金融场景:行业轮动策略,板块配置
# ==================== 创建行业数据 ====================
industries_network = ['金融', '科技', '消费', '医药', '能源', '地产'] # 行业列表
n_industries = len(industries_network) # 行业数量
# ==================== 生成相关行业数据 ====================
# 金融-地产高相关,科技-消费高相关
industry_corr = pd.DataFrame(
[[1.00, 0.30, 0.35, 0.25, 0.40, 0.60], # 金融的相关性
[0.30, 1.00, 0.55, 0.30, 0.25, 0.20], # 科技的相关性
[0.35, 0.55, 1.00, 0.35, 0.20, 0.25], # 消费的相关性
[0.25, 0.30, 0.35, 1.00, 0.15, 0.20], # 医药的相关性
[0.40, 0.25, 0.20, 0.15, 1.00, 0.35], # 能源的相关性
[0.60, 0.20, 0.25, 0.20, 0.35, 1.00]], # 地产的相关性
index=industries_network, columns=industries_network # 设置行名和列名
)
# 对角线为1(自相关)
# 金融-地产=0.60(高相关,同属周期板块)
# 科技-消费=0.55(高相关,消费升级主题)
print('行业相关系数矩阵:') # 打印标题
print(industry_corr) # 打印相关系数矩阵
# ==================== 绘制热力图 ====================
plt.figure(figsize=(10, 8)) # 创建画布
mask = np.triu(np.ones_like(industry_corr, dtype=bool)) # 创建上三角掩码
sns.heatmap(industry_corr,
annot=True, fmt='.2f', # 显示数值,保留2位小数
cmap='RdYlGn', center=0,
square=True, linewidths=0.5,
mask=mask, # 只显示下三角,避免重复
vmin=-1, vmax=1)
plt.title('行业相关性结构', fontsize=16, fontweight='bold')
plt.tight_layout()
plt.show()
# ==================== 识别高度相关的行业对 ====================
high_corr_pairs = [] # 存储高相关行业对
for i in range(n_industries): # 遍历所有行业
for j in range(i+1, n_industries): # 遍历i之后的所有行业
corr_val = industry_corr.iloc[i, j] # 提取相关系数
if abs(corr_val) > 0.5: # 如果绝对值>0.5
high_corr_pairs.append((industries_network[i], industries_network[j], corr_val))
# 添加到列表
print('\n高度相关行业对(|ρ| > 0.5):') # 打印标题
for pair in sorted(high_corr_pairs, key=lambda x: abs(x[2]), reverse=True):
# 按相关系数绝对值降序排列
print(f'{pair[0]} - {pair[1]}: {pair[2]:.2f}') # 打印行业对和相关系数行业轮动策略:
基于相关性的行业轮动:
- 高相关行业:同涨同跌,避免过度配置
- 低相关行业:分散化效果好
- 相关变化:预判板块轮动
60.9 偏相关系数
# =============================================================================
# 题目: 偏相关系数——控制其他变量的影响
# =============================================================================
# 本代码展示如何计算偏相关系数,剔除第三方变量的影响
# 应用于金融场景:因子分析,识别真实的变量关系
# ==================== 导入必要的库 ====================
from scipy.stats import pearsonr # 导入Pearson相关系数函数
# ==================== 创建三变量数据 ====================
# X:市场收益率
# Y:个股收益率
# Z:行业因子
np.random.seed(42) # 设置随机种子
n = 500 # 样本量
market = np.random.normal(0.001, 0.02, n) # 市场收益率:均值0.1%,标准差2%
industry_factor = np.random.normal(0, 0.01, n) # 行业因子:均值0,标准差1%
stock = 0.8 * market + 0.5 * industry_factor + np.random.normal(0, 0.015, n)
# 个股收益率 = 0.8×市场 + 0.5×行业因子 + 特质风险
df_partial = pd.DataFrame({ # 创建DataFrame
'市场': market,
'个股': stock,
'行业因子': industry_factor
})
# ==================== 计算简单相关系数 ====================
corr_market_stock = df_partial['市场'].corr(df_partial['个股'])
corr_industry_stock = df_partial['行业因子'].corr(df_partial['个股'])
print('简单相关系数:')
print(f'市场-个股: {corr_market_stock:.4f}')
print(f'行业因子-个股: {corr_industry_stock:.4f}')
# ==================== 计算偏相关系数(控制行业因子) ====================
# 方法1:回归残差法
from sklearn.linear_model import LinearRegression # 导入线性回归模型
# 个股对行业因子回归
model_stock = LinearRegression().fit(
df_partial[['行业因子']], # 自变量:行业因子
df_partial['个股'] # 因变量:个股收益率
)
residual_stock = df_partial['个股'] - model_stock.predict(df_partial[['行业因子']])
# 残差 = 实际值 - 预测值(剔除行业影响后的个股收益)
# 市场对行业因子回归
model_market = LinearRegression().fit(
df_partial[['行业因子']], # 自变量:行业因子
df_partial['市场'] # 因变量:市场收益率
)
residual_market = df_partial['市场'] - model_market.predict(df_partial[['行业因子']])
# 残差 = 实际值 - 预测值(剔除行业影响后的市场收益)
# ==================== 偏相关系数 = 残差的相关系数 ====================
partial_corr = np.corrcoef(residual_market, residual_stock)[0, 1]
# 计算残差的相关系数,即为偏相关系数
print(f'\n偏相关系数(控制行业因子):')
print(f'市场-个股: {partial_corr:.4f}')
print(f'解释: 剔除行业影响后,市场与个股的真实相关性')偏相关的含义:
偏相关系数 \(\rho_{XY|Z}\):控制变量 \(Z\) 后,\(X\) 和 \(Y\) 的相关性。
金融应用:
- Alpha分离:剔除市场因子后,股票的特质收益
- 因子正交化:构建正交因子
- 归因分析:分离不同来源的影响
60.10 本章小结
要点:
- 样本协方差 \(\text{Cov}(X,Y)=\frac{1}{n-1}\sum_{i=1}^n(x_i-\bar{x})(y_i-\bar{y})\) 带量纲、取值无界;相关系数 \(\rho=\text{Cov}(X,Y)/(\sigma_X\sigma_Y)\) 无量纲、落在 \([-1,1]\),不同变量对之间可直接比较
- 协方差矩阵 \(\Sigma\) 是对称矩阵,对角线为各变量的方差;
df.cov()与df.corr()一步得到两个矩阵;日频数据的协方差与方差年化乘252,标准差年化乘 \(\sqrt{252}\) - 组合方差 \(\sigma_p^2=\mathbf{w}^T\Sigma\mathbf{w}\) 展开后包含所有两两协方差项;只要资产间不完全同涨同跌(\(\rho_{ij}<1\)),组合标准差就低于各资产标准差的加权平均,这就是分散化的数学来源
- 平台任务的笔误类型:缺
import、以减号代等号、变量名含空格、赋值号右侧悬空(corr =、myfont=);平台按原始题面判定,本地演练需先修复,再用同结构数据复现计算 - 平台任务2的任务二中
formula = 'sales ~ points + C(market) * C(warranty)'把分类因子与连续协变量一起纳入回归,属协方差分析(ANCOVA)的进阶内容 - 滚动相关系数(
rolling(window).corr)刻画相关性的时变特征;偏相关系数等于两组残差的相关系数,用于剔除第三变量的影响
易错点:
- 相关系数只捕捉线性关系,\(\rho\) 接近0时仍可能存在强非线性关系;样本量小或存在离群点时相关系数极不稳定
- 协方差大小受计量单位影响(用元与用万元会得到不同数值),跨资产对的比较必须用相关系数
- 相关不等于因果:高相关的两个资产可能只是共同受第三个因子驱动,需用偏相关或因子模型进一步检验
- 平台任务1的
sale_points-pd.read_csv(...)(减号)与sale points.head()(变量名含空格)在Python中无法运行,属题面笔误,在平台上输入时请保持原样 - 本章两处本地演练版与多数示例使用内联或随机生成的模拟数据,只为演示语法与统计性质,真实研究应换成实际数据
- 组合方差的矩阵式中权重向量与协方差矩阵的顺序、维度要匹配,
np.dot嵌套写反会报维度错误
60.11 动手与思考
以下练习每题附参考答案(默认折叠)。请先独立完成并写下你的判断,再点开对照,最后上机验证。
自测回忆:不看正文,写出样本协方差公式并回答:分母为什么是 \(n-1\) 而不是 \(n\);\(\text{Cov}(X,X)\) 等于什么;相关系数的取值范围是多少。
参考答案(点开前请先独立完成)
解题思路:样本协方差 \(s_{XY} = \frac{1}{n-1}\sum_{i=1}^{n}(x_i-\bar{x})(y_i-\bar{y})\)。分母取 \(n-1\) 的原因:均值 \(\bar{x}\)、\(\bar{y}\) 是由同一组样本估计出来的,离差之和恒为零,n 个离差中只有 n−1 个自由变动(损失 1 个自由度),除以 n−1 才是总体协方差的无偏估计(与样本方差同口径)。\(\text{Cov}(X,X) = \text{Var}(X)\)——变量与自身的协方差就是它的方差,这也解释了协方差矩阵对角线是各变量方差的原因。相关系数 \(\rho = \text{Cov}(X,Y)/(\sigma_X\sigma_Y)\) 无量纲,取值范围 \([-1, 1]\)(柯西–施瓦茨不等式保证)。
回扣主线:样本协方差、协方差矩阵与相关系数的定义见本章”数学基础”一节,矩阵实现见第 章节 7 章。
输出预测:不运行代码,先写出下面代码的输出结果,再上机检验你的判断。
import pandas as pd df = pd.DataFrame({'A': [1, 2, 3], 'B': [2, 4, 6]}) print(df.cov().round(2).values.tolist()) print(df.corr().round(4).values.tolist())参考答案(先写下你的预测再点开)
解题思路:A 的均值 2、B 的均值 4,离差 A 为 [−1, 0, 1]、B 为 [−2, 0, 2]。方差 \(s_A^2 = (1+0+1)/2 = 1\),\(s_B^2 = (4+0+4)/2 = 4\),协方差 \((2+0+2)/2 = 2\),故协方差矩阵为 [[1, 2], [2, 4]](对称、对角线为方差)。B 恰为 A 的 2 倍,完全正线性关系,相关系数 \(2/\sqrt{1\times4} = 1\),四个元素全为 1。
# 验证脚本:协方差矩阵与相关系数矩阵 import pandas as pd # 导入pandas库 df = pd.DataFrame({'A': [1, 2, 3], 'B': [2, 4, 6]}) # B=2A的严格线性关系 print(df.cov().round(2).values.tolist()) # 协方差矩阵(ddof=1样本口径) print(df.corr().round(4).values.tolist()) # 相关系数矩阵预期输出(本机 peter 环境实际运行结果,具体以平台运行结果为准):
[[1.0, 2.0], [2.0, 4.0]] [[1.0, 1.0], [1.0, 1.0]]回扣主线:协方差矩阵的对称性与”标准差口径一致时相关系数为 1”见本章”协方差矩阵计算”一节与第 章节 7 章。
输出预测:两资产的等权组合,先预测三行输出,再上机检验(第三行是两资产标准差各按50%加权的结果)。
import numpy as np w = np.array([0.5, 0.5]) cov = np.array([[0.04, 0.01], [0.01, 0.09]]) var = w @ cov @ w weighted = 0.5 * np.sqrt(0.04) + 0.5 * np.sqrt(0.09) print(round(var, 4)) print(round(np.sqrt(var), 4)) print(round(weighted, 4))参考答案(先写下你的预测再点开)
解题思路:组合方差 \(\sigma_p^2 = \mathbf{w}^T\Sigma\mathbf{w} = 0.25\times0.04 + 2\times0.25\times0.01 + 0.25\times0.09 = 0.0375\);开方得组合标准差约 0.1936;两资产标准差分别为 0.2 与 0.3,各按 50% 加权为 0.25。0.1936 < 0.25 即分散化效应:两资产相关系数 \(\rho = 0.01/\sqrt{0.04\times0.09} = 1/6 < 1\),不完全同涨同跌,组合风险严格低于标准差的加权平均。
# 验证脚本:等权组合方差与加权平均标准差 import numpy as np # 导入NumPy库 w = np.array([0.5, 0.5]) # 等权权重向量 cov = np.array([[0.04, 0.01], [0.01, 0.09]]) # 两资产协方差矩阵 var = w @ cov @ w # 矩阵式计算组合方差 weighted = 0.5 * np.sqrt(0.04) + 0.5 * np.sqrt(0.09) # 标准差加权平均 print(round(var, 4)) # 组合方差 print(round(np.sqrt(var), 4)) # 组合标准差(波动率) print(round(weighted, 4)) # 标准差加权平均(分散化上限)预期输出(本机 peter 环境实际运行结果,具体以平台运行结果为准):
0.0375 0.1936 0.25回扣主线:组合方差矩阵式与分散化的数学来源见本章”组合风险与协方差”一节与第 章节 7 章。
变式任务(本地演练版同型改造):在
lst-ch59-local-task-1内联数据的基础上,计算sale_points['points']与sale_points['sales']的协方差与相关系数,按平台任务”abs(corr)是否大于等于0.7”的口径给出判断;再按market分成三个子样本分别计算该相关系数,观察组内相关与总体相关是否一致。参考答案(点开前请先独立完成)
解题思路:促销积分与销售额在内联的 10 条记录上高度正相关——协方差 204.0556、相关系数 0.9992,按 0.7 阈值口径判断”存在显著的线性相关性”(True)。分市场看:market 1(4 条)为 0.9993、market 2(3 条)为 0.982、market 3(3 条)为 0.999,组内相关与总体相关方向与强度一致(都极强、无符号翻转),不存在”总体强、组内弱”的辛普森悖论式反差;market 2 略低只是因为组内只有 3 个观测、线性贴合稍差。协方差数值 204.06 带有量纲(积分×万元),跨数据集不可比,比较强弱应看相关系数。
# 变式脚本:总体与分组的相关性计算 import pandas as pd # 导入pandas库 sale_points = pd.DataFrame({ # 复用lst-ch59-local-task-1的内联数据 'points': [30, 33, 37, 40, 44, 47, 50, 53, 57, 60], # 促销积分 'sales': [88, 94, 101, 106, 116, 120, 128, 133, 141, 148], # 月销售额 'market': ['market 1']*4 + ['market 2']*3 + ['market 3']*3, # 所在市场 'warranty': ['1 years', '3 years']*5}) # 保修期方案 cov_ps = sale_points['points'].cov(sale_points['sales']) # 两列协方差 corr_ps = sale_points['points'].corr(sale_points['sales']) # 两列相关系数 print(round(cov_ps, 4), round(corr_ps, 4), abs(corr_ps) >= 0.7) # 总体口径与0.7阈值判断 for mk, sub in sale_points.groupby('market'): # 按市场分组 print(mk, round(sub['points'].corr(sub['sales']), 4), len(sub)) # 组内相关与样本量预期输出(本机 peter 环境实际运行结果,具体以平台运行结果为准):
204.0556 0.9992 True market 1 0.9993 4 market 2 0.982 3 market 3 0.999 3回扣主线:协方差受量纲影响、比较强弱应看相关系数的告诫见本章”要点”第 1 条与第 章节 7 章;分组比较的口径与第 章节 34 章一致。
思考题:两个资产的相关系数为0.6(正相关),为什么等权组合的风险仍然低于两者标准差的平均?请从 \(\sigma_p^2=w_1^2\sigma_1^2+w_2^2\sigma_2^2+2w_1w_2\rho\sigma_1\sigma_2\) 中交叉项的作用出发解释。
参考答案(点开前请先独立完成)
解题思路:等权时 \(\sigma_p^2 = \frac{\sigma_1^2+\sigma_2^2}{4} + \frac{\rho\sigma_1\sigma_2}{2}\)。组合风险能否降到加权平均以下,全在交叉项 \(2w_1w_2\rho\sigma_1\sigma_2\):若两资产完全同涨同跌(\(\rho=1\)),交叉项取最大值 \(2w_1w_2\sigma_1\sigma_2\),此时 \(\sigma_p = w_1\sigma_1 + w_2\sigma_2\) 恰等于标准差的加权平均,分散化毫无收益;只要 \(\rho<1\)(哪怕仍为正,如 0.6),交叉项就严格小于该最大值,\(\sigma_p\) 也就严格低于加权平均——省下的那部分风险正是两资产波动”错拍”(不同步)抵消掉的。极端地,\(\rho=0\) 时交叉项消失,\(\sigma_p=\sqrt{w_1^2\sigma_1^2+w_2^2\sigma_2^2}\) 更低;\(\rho=-1\) 时两资产的波动可完全对冲。数值印证见本题第 3 问:\(\rho=1/6\) 的等权组合标准差 0.1936 低于加权平均 0.25。
回扣主线:交叉项与分散化原理的推导见本章”组合风险与协方差”一节”分散化原理”,与第 章节 7 章、第 章节 34 章的相关性主线呼应。